﻿using JESAI.DistributedLock.Internal;
using System;
using System.Data;

namespace JESAI.DistributedLock.SqlServer;

/// <summary>
/// 指定连接到SQL数据库并对其进行锁定的选项
/// </summary>
public sealed class SqlConnectionOptionsBuilder
{
    private TimeoutValue? _keepaliveCadence;
    private bool? _useTransaction, _useMultiplexing;

    internal SqlConnectionOptionsBuilder() { }

    /// <summary>
    /// 由于Azure积极的连接管理器会主动关闭空闲连接，因此将SQL Azure用作分布式同步提供程序可能具有挑战性。
    /// 
    /// 为防止这种情况，此选项设置了我们在持有锁的连接上运行无操作“保持活动”查询的节奏。
    /// 请注意，这仍然不能保证在所有可能导致管理器关闭连接的情况下保护连接。
    /// 
    /// 要禁用保持活动，请设置为<see cref="Timeout.InfiniteTimeSpan"/>。
    /// 
    /// 根据Azure的30分钟默认行为，默认为10分钟。
    /// 
    /// 有关更多信息，请参阅https://github.com/madelson/DistributedLock/issues/5上的讨论。
    /// </summary>

    public SqlConnectionOptionsBuilder KeepaliveCadence(TimeSpan keepaliveCadence)
    {
        this._keepaliveCadence = new TimeoutValue(keepaliveCadence, nameof(keepaliveCadence));
        return this;
    }

    /// <summary>
    /// 同步是否应该使用事务范围而不是会话范围。默认为false。
    /// 
    /// 基于事务的同步比使用连接稍微便宜一些，
    /// 因为释放只需要释放底层的<see cref="IDbTransaction"/>。
    /// 
    /// 缺点是使用这种策略可能会导致长时间运行的事务，这对于使用完整恢复模型的数据库来说可能是个问题。此外，这种策略使我们无法利用<see cref="UseMultiplexing(bool)"/>及其性能优势。
    /// </summary>

    public SqlConnectionOptionsBuilder UseTransaction(bool useTransaction = true)
    {
        this._useTransaction = useTransaction;
        return this;
    }

    /// <summary>
    /// 这种模式利用了这样一个事实：在“持有”锁（或其他同步原语）时，连接基本上是空闲的。因此，与其为每个持有的锁创建一个新连接，通常可以复用一个共享连接，以便该连接可以同时持有多个锁。
    /// 
    /// 复用默认是开启的，除非<see cref="UseTransaction(bool)"/>被设置为TRUE，在这种情况下复用会被禁用，因为它与<see cref="UseTransaction(bool)"/>不兼容。
    /// 
    /// 这种实现方式使得释放这样一个连接上持有的锁永远不会被等待在同一连接上获取锁的Acquire()调用阻塞。因此，复用策略是“乐观的”：如果在共享连接上不能立即获取锁，将分配一个新的（可共享的）连接。
    /// 
    /// 此选项可以在高负载场景下提高性能并避免连接池耗尽。它也特别适用于<see cref="IDistributedLock.TryAcquire(TimeSpan, System.Threading.CancellationToken)"/>语义与零长度超时一起使用的情况。
    /// </summary>

    public SqlConnectionOptionsBuilder UseMultiplexing(bool useMultiplexing = true)
    {
        this._useMultiplexing = useMultiplexing;
        return this;
    }

    internal static (TimeoutValue keepaliveCadence, bool useTransaction, bool useMultiplexing) GetOptions(Action<SqlConnectionOptionsBuilder>? optionsBuilder)
    {
        SqlConnectionOptionsBuilder? options;
        if (optionsBuilder != null)
        {
            options = new SqlConnectionOptionsBuilder();
            optionsBuilder(options);
        }
        else
        {
            options = null;
        }

        var keepaliveCadence = options?._keepaliveCadence ?? TimeSpan.FromMinutes(10);
        var useTransaction = options?._useTransaction ?? false;
        var useMultiplexing = options?._useMultiplexing ?? !options?._useTransaction ?? true;

        if (useMultiplexing && useTransaction)
        {
            throw new ArgumentException(nameof(UseTransaction) + ": is not compatible with " + nameof(UseMultiplexing));
        }

        return (keepaliveCadence, useTransaction, useMultiplexing);
    }
}
